package org.zjt.agent;

import org.apache.http.util.Asserts;
import org.zjt.agent.convertor.RedefineClassConverter;
import org.zjt.agent.generate.*;
import org.zjt.agent.generate.bytebuddy.ByteBuddyByteCodeGenerator;
import org.zjt.agent.generate.bytebuddy.PersonClassHandler;
import org.zjt.agent.generate.javassist.JavassistByteCodeGenerator;

import java.lang.instrument.ClassFileTransformer;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * Description:
 *
 * @author juntao.zhang
 * Date: 2018-07-05 下午2:13
 * @see
 */

public class ClassFileTransformerLoader {


    public static ClassFileTransformer load() {
        return load(System.getProperty("byteCodeGeneratorName",GeneratorEnum.BYTE_BUDDY.getName()));
    }


    private static List<ClassHandler> loadClassHandlers(GeneratorEnum generatorEnum) {

        List<ClassHandler> classHandlerList = AccessController.doPrivileged((PrivilegedAction<List<ClassHandler>>) () -> {
            List<ClassHandler> classHandlers = new ArrayList<>();
            ServiceLoader<ClassHandler> serviceLoader = ServiceLoader.load(ClassHandler.class);
            Iterator<ClassHandler> it = serviceLoader.iterator();
            while (it != null && it.hasNext()) {
                ClassHandler classHandler = it.next();
                GeneratorEnum generator = classHandler.getGenerator();
                if (Objects.equals(generator, generatorEnum)) {
                    classHandlers.add(classHandler);
                }
            }
            return classHandlers;
        });
        return classHandlerList;
    }


    private static List<MethodRedefineStrategy> loadMethodRedefineStrategys(Class<? extends ClassHandler> classClassHandler) {
        List<MethodRedefineStrategy> methodRedefineStrategieList = AccessController.doPrivileged((PrivilegedAction<List<MethodRedefineStrategy>>) () -> {
            List<MethodRedefineStrategy> methodRedefineStrategies = new ArrayList<>();
            ServiceLoader<MethodRedefineStrategy> serviceLoader = ServiceLoader.load(MethodRedefineStrategy.class);
            Iterator<MethodRedefineStrategy> it = serviceLoader.iterator();
            while (it != null && it.hasNext()) {
                MethodRedefineStrategy methodRedefineStrategy = it.next();
                if (Objects.equals(methodRedefineStrategy.getClassHandlerType(), classClassHandler)) {
                    methodRedefineStrategies.add(methodRedefineStrategy);
                }
            }
            return methodRedefineStrategies;
        });
        return methodRedefineStrategieList;
    }



    private static ByteCodeGenerator loadByteCodeGenerator(GeneratorEnum generatorEnum) {
        List<ClassHandler> classHandlers = loadClassHandlers(generatorEnum);

        classHandlers = classHandlers.stream().map(item -> {
            List<MethodRedefineStrategy> methodRedefineStrategies = loadMethodRedefineStrategys(item.getClass());
            item.addMethodRedefineStrategies(methodRedefineStrategies);
            return item;
        }).collect(Collectors.toList());


        if (Objects.equals(generatorEnum,GeneratorEnum.BYTE_BUDDY)) {
            return new ByteBuddyByteCodeGenerator(classHandlers);
        }
        if (Objects.equals(generatorEnum,GeneratorEnum.JAVASSIST)) {
            return new JavassistByteCodeGenerator(classHandlers);
        }
        throw new UnsupportedOperationException(generatorEnum + "is not matcher ByteCodeGenerator");
    }




    private static ClassFileTransformer load(String byteCodeGeneratorName) {
        Optional<GeneratorEnum> generatorEnumOptional = GeneratorEnum.ofName(byteCodeGeneratorName);
        Asserts.check(generatorEnumOptional.isPresent(),byteCodeGeneratorName + "  byteCodeGeneratorName 配置异常");
        GeneratorEnum generatorEnum = generatorEnumOptional.get();
        ByteCodeGenerator byteCodeGenerator = loadByteCodeGenerator(generatorEnum);
        ByteCodeGenerateAdapter byteCodeGenerateAdapter = new ByteCodeGenerateAdapter(byteCodeGenerator);
        RedefineClassConverter redefineClassConverter = new RedefineClassConverter(byteCodeGenerateAdapter);
        MyClassFileTransformer myClassFileTransformer = new MyClassFileTransformer(redefineClassConverter);
        return myClassFileTransformer;
    }

}
